import numpy as np
import matplotlib.pyplot as plt
import datetime as dt
import yfinance as yf
"ggplot") plt.style.use(
\[
C A G R=\left[\left(\frac{E V}{B V}\right)^{1/n}-1\right]\times 100
\] where: \(E V=\) Ending value
\(B V=\) Beginning value
\(n=\) Number of years
= yf.download(
sp500 "^GSPC"],
[=dt.datetime.today() - dt.timedelta(days=1500),
start=dt.datetime.today(),
end=True,
progress="inline",
actions="1d",
interval )
[*********************100%***********************] 1 of 1 completed
This function is for daily data, because we use 252
number of trading days in a year to calculate \(n\).
def get_cagr(df):
= df["Close"][-1]
EV = df["Close"][0]
BV = len(df) / 252
n = (EV / BV) ** (1 / n) - 1
cagr print("CAGR: {:.2f}%".format(cagr * 100))
return cagr
= get_cagr(sp500) cagr
CAGR: 14.62%
/tmp/ipykernel_4295/3211283570.py:2: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
/tmp/ipykernel_4295/3211283570.py:3: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
Volatility
Simple as you have imagined, volatility is commonly measured by standard deviation. The only thing you need to take heed is to know how to convert to annualized volatility. \[ \text{daily return} \times \sqrt{252}\\ \text{weekly return} \times \sqrt{52}\\ \text{monthly return} \times \sqrt{12} \]
def get_volatility(df, freq):
"""
The function is for using daily trading data.
"""
= df["Close"].pct_change().std()
daily_ret if freq == "daily":
= daily_ret * np.sqrt(252)
vol elif freq == "weekly":
= daily_ret * np.sqrt(52)
vol elif freq == "monthly":
= daily_ret * np.sqrt(12)
vol return vol
"daily") get_volatility(sp500,
0.1664305706560202
Max Drawdown and Calmar Ratio
Max drawdown is the percentage counts the maximum drop from peak return in a certain period.
def get_max_dd(df):
"daily_ret"] = df["Close"].pct_change()
df["cum_ret"] = (1 + df["daily_ret"]).cumprod()
df["cum_trailing_max"] = df["cum_ret"].cummax()
df["drawdown"] = df["cum_trailing_max"] - df["cum_ret"]
df["drawdown_pct"] = df["drawdown"] / df["cum_trailing_max"]
df[= df["drawdown_pct"].max()
max_dd print("Max drawdown: {:.4f}%".format(max_dd * 100))
return max_dd
= get_max_dd(cisco) mdd
Max drawdown: 42.8079%
Calmar ratio is similar to Sharpe ratio, but the risk is replace by maximum drawdown. A hedge fund manager named Terry W. Young invented Calmar ratio, and ‘Calmar’ is an acronym of his company’s name and its newsletter: CALifornia Managed Accounts Reports.
def get_calmar(df):
= get_cagr(df) / get_max_dd(df)
calmar print("Calmar ratio: {:.4f}%".format(calmar))
return calmar
get_calmar(cisco)
CAGR: 7.60%
Max drawdown: 42.8079%
Calmar ratio: 0.1775%
/tmp/ipykernel_4295/3211283570.py:2: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
/tmp/ipykernel_4295/3211283570.py:3: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
0.17752693121492458